package com.timguan.render.web.extand;

import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import org.jsoup.Jsoup;
import org.jsoup.helper.StringUtil;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author guankaiqiang
 */
public class PriceRenderFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(PriceRenderFilter.class);
    private String url = "http://m.tepin.hk/shop/prostock/findSimpleInfo.html";
    private OkHttpClient client = null;
    private static ThreadLocal<StopWatch> priceRenderStopWath = new ThreadLocal<StopWatch>();

    private static StopWatch getPriceRenderStopWatch() {
        StopWatch stopWatch = priceRenderStopWath.get();
        if (stopWatch == null) {
            stopWatch = new StopWatch();
            priceRenderStopWath.set(stopWatch);
        }
        return stopWatch;
    }

    private static void cleanPriceRenderStopWatch() {
        priceRenderStopWath.remove();
    }


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        ConnectionPool connectionPool = new ConnectionPool(5, 5, TimeUnit.MINUTES);
        client = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(15, TimeUnit.SECONDS)
                .connectionPool(connectionPool)
                .retryOnConnectionFailure(true)
                .build();
        url = filterConfig.getInitParameter("url");
    }

    /**
     * 商品节点
     */
    static class ItemElementWraper {
        private String itemId;
        private Element element;

        public ItemElementWraper(String itemId, Element element) {
            this.itemId = itemId;
            this.element = element;
        }

        public String getItemId() {
            return itemId;
        }

        public void setItemId(String itemId) {
            this.itemId = itemId;
        }

        public Element getElement() {
            return element;
        }

        public void setElement(Element element) {
            this.element = element;
        }
    }

    /**
     * 解析商品节点
     *
     * @param serverRenderPriceModules 需要服务端渲染的价格模块
     * @return
     */
    private List<ItemElementWraper> parseItemElements(Elements serverRenderPriceModules) {
        getPriceRenderStopWatch().start("parseItemElements");
        List<ItemElementWraper> itemElementsList = new ArrayList<ItemElementWraper>();
        for (Element priceModule : serverRenderPriceModules) {
            Elements itemElements = priceModule.select(".cms-src-item");
            for (Element itemElement : itemElements) {
                String itemId = itemElement.attr("data-cms-itemid");
                if (!StringUtil.isBlank(itemId)) {
                    itemElementsList.add(new ItemElementWraper(itemId, itemElement));
                }
            }
        }
        getPriceRenderStopWatch().stop();
        return itemElementsList;
    }

    /**
     * 获取商品ID
     *
     * @param itemElementWrapers
     * @return
     */
    private Set<String> getItemIds(List<ItemElementWraper> itemElementWrapers) {
        if (itemElementWrapers != null && itemElementWrapers.size() != 0) {
            Set<String> itemIds = new HashSet<String>();
            for (ItemElementWraper itemElementWraper : itemElementWrapers) {
                itemIds.add(itemElementWraper.getItemId());
            }
            return itemIds;
        }
        return null;
    }


    /**
     * 价格获取
     *
     * @param itemIds
     * @return
     */
    private List<ItemPriceAdapter> getItemPrices(Collection<String> itemIds) {
        List<ItemPriceAdapter> result = null;
        if (itemIds == null || itemIds.size() == 0) {
            return null;
        }
        getPriceRenderStopWatch().start("getItemPrices");
        try {
            RequestBody body = new FormBody.Builder()
                    .add("channel", "Mobile")
                    .add("singleCodes", StringUtil.join(itemIds, ","))//String.join(",", itemIds))
                    .build();
            Request request = new Request.Builder()
                    .url(url)
                    .header("host", "m.tepin.hk")
                    .post(body)
                    .build();
            Response response = client.newCall(request).execute();
            String responseBody = response.body().string();
            JSONObject jsonObject = com.alibaba.fastjson.JSON.parseObject(responseBody);
            if (jsonObject != null && jsonObject.getInteger("returnCode") == 0 && jsonObject.getString("object") != null) {
                result = com.alibaba.fastjson.JSON.parseArray(jsonObject.getString("object"), ItemPriceAdapter.class);
                if (itemIds.size() != result.size()) {
                    logger.error("item size not equals,request:{}, size:{},result:{}, size:{}",
                            itemIds.size(), Arrays.toString(itemIds.toArray()),
                            responseBody, result.size());
                }
                for (ItemPriceAdapter itemPriceAdapter : result) {
                    itemPriceAdapter.setItemId(itemPriceAdapter.getCode());
                    itemPriceAdapter.setDiscount(itemPriceAdapter.getPrice() != 0 && itemPriceAdapter.getOriginPrice() != 0 ?
                            new DecimalFormat("#.0").format((float) itemPriceAdapter.getPrice() * 10 / itemPriceAdapter.getOriginPrice()) : "");
                }
            } else {
                logger.error("getPrice failed!itemId:{},response:{}", itemIds.toString(), responseBody);
            }
        } catch (Throwable throwable) {
            logger.error("getPrice failed!itemIds:" + itemIds.toString(), throwable);
        }
        getPriceRenderStopWatch().stop();
        return result;
    }

    /**
     * 商品价格
     */
    static class ItemPriceAdapter {
        String itemId;
        String code;
        int originPrice;
        int price;
        int stock;
        String discount;

        public String getItemId() {
            return itemId;
        }

        public void setItemId(String itemId) {
            this.itemId = itemId;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public int getOriginPrice() {
            return originPrice;
        }

        public void setOriginPrice(int originPrice) {
            this.originPrice = originPrice;
        }

        public int getPrice() {
            return price;
        }

        public void setPrice(int price) {
            this.price = price;
        }

        public int getStock() {
            return stock;
        }

        public void setStock(int stock) {
            this.stock = stock;
        }

        public String getDiscount() {
            return discount;
        }

        public void setDiscount(String discount) {
            this.discount = discount;
        }

        @Override
        public String toString() {
            return "ItemPrice{" +
                    "code='" + code + '\'' +
                    ", originPrice=" + originPrice +
                    ", price=" + price +
                    ", stock=" + stock +
                    ", discount='" + discount + '\'' +
                    '}';
        }
    }

    /**
     * 价格渲染
     *
     * @param itemPriceAdapters
     * @param itemElementWrapers
     */
    private void renderPrices(List<ItemPriceAdapter> itemPriceAdapters, List<ItemElementWraper> itemElementWrapers) {
        getPriceRenderStopWatch().start("renderPrices");
        if (CollectionUtils.isEmpty(itemElementWrapers) || CollectionUtils.isEmpty(itemElementWrapers)) {
            return;
        }
        for (ItemPriceAdapter itemPriceAdapter : itemPriceAdapters) {
            for (int index = 0; index < itemElementWrapers.size(); index++) {
                if (itemPriceAdapter.getItemId().equalsIgnoreCase(itemElementWrapers.get(index).getItemId())) {
                    fillPrice(itemPriceAdapter, itemElementWrapers.get(index));
                }//todo 算法优化
            }
        }
        getPriceRenderStopWatch().stop();
    }

    /**
     * 单品价格填充
     *
     * @param itemPriceAdapter
     * @param itemElementWraper
     * @return
     */
    private ItemElementWraper fillPrice(ItemPriceAdapter itemPriceAdapter, ItemElementWraper itemElementWraper) {
        Element itemElement = itemElementWraper.getElement();
        Element fillPrice = itemElement.select(".cms-fill-price").first();
        if (fillPrice != null) {
            fillPrice.text(String.valueOf(itemPriceAdapter.getPrice()));
        }
        if (itemPriceAdapter.getPrice() >= itemPriceAdapter.getOriginPrice() || itemPriceAdapter.getOriginPrice() == 0) {
            itemElement.select(".cms-fill-discountparent").remove();
            itemElement.select(".cms-fill-referencepriceparent").remove();
        } else {
            Element fillOriginPrice = itemElement.select(".cms-fill-referenceprice").first();
            if (fillOriginPrice != null) {
                fillOriginPrice.text(String.valueOf(itemPriceAdapter.getOriginPrice()));
            }
            Element fillDiscount = itemElement.select(".cms-fill-discount").first();
            if (fillDiscount != null) {
                fillDiscount.text(String.valueOf(itemPriceAdapter.getDiscount()));
            }
        }
        if (itemPriceAdapter.getStock() <= 0) {
            Element fillSoldout = itemElement.select(".cms-fill-soldout").first();
            if (fillSoldout != null) {
                String style = fillSoldout.attr("style");
                if (style != null) {
                    style = style.replace("none", "inline");
                } else {
                    style = "display:inline";
                }
                fillSoldout.attr("style", style);
            }
        }
        return itemElementWraper;
    }

    /**
     * 服务器渲染价格
     *
     * @param origin
     * @return
     */
    private String serverRenderPrice(String origin) {
        Document doc = Jsoup.parse(origin);
        Elements priceModules = doc.body().select(".cms-module-fillprice");
        Elements serverRenderPriceModules = new Elements();
        for (Element priceModule : priceModules) {
            if (priceModule.hasClass("server-render")) {
                priceModule.addClass("price-loaded");//标记价格已经加载，客户端无需重复渲染
                serverRenderPriceModules.add(priceModule);
            }
        }
        List<ItemElementWraper> itemElementWrapers = parseItemElements(serverRenderPriceModules);
        Set<String> itemIds = getItemIds(itemElementWrapers);
        List<ItemPriceAdapter> itemPrices = getItemPrices(itemIds);
        renderPrices(itemPrices, itemElementWrapers);
        if (getPriceRenderStopWatch().getTotalTimeSeconds() > 15) {
            logger.info("render price cost more than 15s", getPriceRenderStopWatch().prettyPrint());
        }
        return doc.html();
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            CharResponseWrapper responseWrapper = new CharResponseWrapper(
                    (HttpServletResponse) servletResponse);
            filterChain.doFilter(servletRequest, responseWrapper);
            servletResponse.getWriter().write(serverRenderPrice(responseWrapper.toString()));
        } catch (Exception e) {
            logger.error("[PriceRenderFilter]render price failed!", e);
            throw new RuntimeException("[PriceRenderFilter]render price failed!", e);
        } finally {
            cleanPriceRenderStopWatch();
        }
    }

    @Override
    public void destroy() {
    }

    public class CharResponseWrapper extends HttpServletResponseWrapper {
        private final CharArrayWriter output;

        @Override
        public String toString() {
            return output.toString();
        }

        public CharResponseWrapper(HttpServletResponse response) {
            super(response);
            output = new CharArrayWriter();
        }

        @Override
        public PrintWriter getWriter() {
            return new PrintWriter(output);
        }
    }
}
